﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using Microsoft.Practices.CompositeWeb.ObjectBuilder.BuildPlan.DynamicMethodPlan.Parameters;
using Microsoft.Practices.ObjectBuilder;
using Microsoft.Practices.CompositeWeb.Interfaces;

namespace WCSFContrib.Extensions.CompositeWeb
{
    /// <summary>
	/// 
	/// </summary>
	public class CreateSharedParameterResolver : ParameterResolver
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="il"></param>
		/// <param name="paramAttr"></param>
		/// <param name="parameterType"></param>
		public override void EmitParameterResolution(ILGenerator il, ParameterAttribute paramAttr, Type parameterType)
		{
            MethodInfo getHeadOfChain = GetPropertyGetter<IBuilderContext>("HeadOfChain", typeof(IBuilderStrategy));
            MethodInfo getLocator = GetPropertyGetter<IBuilderContext>("Locator", typeof(IReadWriteLocator));
            MethodInfo getFromLocator = ObtainGetFromLocatorMethod();
            MethodInfo buildUp = GetMethodInfo<IBuilderStrategy>("BuildUp",
			                                                     typeof (IBuilderContext), typeof (Type), typeof (object),
			                                                     typeof (string));
            MethodInfo getHttpContext = GetMethodInfo<IHttpContextLocatorService>("GetCurrentContext");
            MethodInfo contextItemsGetter = GetPropertyGetter<IHttpContext>("Items", typeof(System.Collections.IDictionary));
            MethodInfo addToItems = GetMethodInfo<System.Collections.IDictionary>("Add", typeof(object), typeof(object));
            MethodInfo itemsContains = GetMethodInfo<System.Collections.IDictionary>("Contains", typeof(object));
            MethodInfo getItemInstace = GetMethodInfo<System.Collections.IDictionary>("get_Item", typeof(object));
            ConstructorInfo keyCtor = GetConstructor<DependencyResolutionLocatorKey>(typeof(Type), typeof(string));

            LocalBuilder httpContextLocator = il.DeclareLocal(typeof(IHttpContextLocatorService));
            LocalBuilder httpContext = il.DeclareLocal(typeof(IHttpContext));
            LocalBuilder itemsDictionary = il.DeclareLocal(typeof(System.Collections.IDictionary));
            LocalBuilder objectInstance = il.DeclareLocal(parameterType);

            CreateSharedAttribute createSharedAttr = (CreateSharedAttribute)paramAttr;

            string objKey = parameterType.ToString() + ";" + createSharedAttr.Key ?? string.Empty;

            // Get locator 
            il.Emit(OpCodes.Ldarg_0);
            il.EmitCall(OpCodes.Callvirt, getLocator, null);

            // Create key
            EmitLoadType(il, typeof(IHttpContextLocatorService));
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Newobj, keyCtor);

            // Look up in locator
            il.EmitCall(OpCodes.Callvirt, getFromLocator, null);
            il.Emit(OpCodes.Stloc, httpContextLocator);

            // Get the current http context from our context locator
            il.Emit(OpCodes.Ldloc, httpContextLocator);
            il.EmitCall(OpCodes.Callvirt, getHttpContext, null);
            il.Emit(OpCodes.Stloc, httpContext);
            // Get the items from the http context
            il.Emit(OpCodes.Ldloc, httpContext);
            il.EmitCall(OpCodes.Callvirt, contextItemsGetter, null);
            il.Emit(OpCodes.Stloc, itemsDictionary);

            Label noPreviousObjectLabel = il.DefineLabel();
            Label doneLabel = il.DefineLabel();

            // Do we have a context?
            il.Emit(OpCodes.Ldloc, httpContext);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Ceq);
            // if context is null we create a new object
            il.Emit(OpCodes.Brtrue, noPreviousObjectLabel); 
            
            // check if we have this key in our dictionary
            il.Emit(OpCodes.Ldloc, itemsDictionary);
            il.Emit(OpCodes.Ldstr, objKey);
            il.EmitCall(OpCodes.Callvirt, itemsContains, null);
            // check if the method returned false and branch to object creation
            il.Emit(OpCodes.Brfalse, noPreviousObjectLabel);

            // if we got this far we did find a previous object in the context, get it and jump to our exit code
            il.Emit(OpCodes.Ldloc, itemsDictionary);
            il.Emit(OpCodes.Ldstr, objKey);
            il.EmitCall(OpCodes.Callvirt, getItemInstace, null);
            il.Emit(OpCodes.Stloc, objectInstance);
            il.Emit(OpCodes.Br, doneLabel);

            // here we create an object and store it in our context
            il.MarkLabel(noPreviousObjectLabel);
			// Get the head of the context chain
			il.Emit(OpCodes.Ldarg_0); // Get context onto the stack
			il.EmitCall(OpCodes.Callvirt, getHeadOfChain, null); // Now head of chain is on the stack
			// Build up parameters to the BuildUp call - context, type, null existing, id
			il.Emit(OpCodes.Ldarg_0); // Push context onto stack
			EmitLoadType(il, parameterType);
			// Existing object is null
			il.Emit(OpCodes.Ldnull);
			// And the id
			il.Emit(OpCodes.Ldarg_3);
			// Call buildup on head of the chain
			il.EmitCall(OpCodes.Callvirt, buildUp, null);
            il.Emit(OpCodes.Stloc, objectInstance);
            // add the created object into the context dictionary
            il.Emit(OpCodes.Ldloc, itemsDictionary);
            il.Emit(OpCodes.Ldstr, objKey);
            il.Emit(OpCodes.Ldloc, objectInstance);
            il.EmitCall(OpCodes.Callvirt, addToItems, null);

            // push the created instance to the top of the stack (this will make it the return value!)
            il.MarkLabel(doneLabel);
            il.Emit(OpCodes.Ldloc, objectInstance);
		}


	}
}
